package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"sync"
	"time"
	"wanfang-translation/internal/service/check"
	"wanfang-translation/internal/service/exception"

	"github.com/golang-collections/collections/queue"
	"github.com/spf13/viper"
	"github.com/streadway/amqp"
)

/*
查重核心业务模块
模块内采用多线程方式执行
线程1：添加检测+开始检测，从rabbit_mq中获取字段，然后将id存储到检测中check_id数组中
线程2：获取检测结果，取check_id数组中的id，获取检测结果，将已经成功的id从检测中数组移动到结果中数组（使用队列）
线程3：获取检测状态，取check_id数组中的id，获取检测状态，将获取完成的id从队列中移除
note: 为了提升删除效率，最好是小组的方式一组整个检测然后整个删除，将失败的下标标记出来，失败重新加入数组末尾
*/

type CheckResult struct {
	checkId string
	paperId string
	// sourceContent string
	targetContent string
	checkLanguage string
	copyPercent   float32
}

// 检测中check_id数组
var checkIdArray []CheckResult

// 定义检测结果队列元素类型

// 检测结果check_id队列
var checkResultQueue = queue.New()

// 检测中数组互斥锁
var muCheckArray sync.Mutex

// 检测结果队列互斥锁
var muCheckQueue sync.Mutex

type FileSavePath struct {
	EnCheckPath string
	ChCheckPath string
}

// 添加检测ID到检测中数组
func AddCheckId(checkResult CheckResult) {
	muCheckArray.Lock()
	checkIdArray = append(checkIdArray, checkResult)
	defer muCheckArray.Unlock()
}

// 批量添加检测ID到检测中数组
func AddCheckIdArray(checkResults []CheckResult) {
	muCheckArray.Lock()
	checkIdArray = append(checkIdArray, checkResults...)
	defer muCheckArray.Unlock()
}

// 移除数组中的子数组, 输入参数为移除子数组的左闭右开区间，如果删除单个元素，end=start + 1
func RemoveCheckIdArray(start, end int) error {
	muCheckArray.Lock()
	defer muCheckArray.Unlock()
	// 需要判断是否数组越界
	if start < 0 || end > len(checkIdArray) || start >= end {
		return errors.New("数组越界")
	}
	checkIdArray = append(checkIdArray[:start], checkIdArray[end:]...)
	return nil
}

// 取子数组
func GetCheckIdArray(start, end int) ([]CheckResult, error) {
	muCheckArray.Lock()
	defer muCheckArray.Unlock()
	// 需要判断是否数组越界
	if start < 0 || end > len(checkIdArray) || start >= end {
		return nil, errors.New("数组越界")
	}
	return checkIdArray[start:end], nil
}

// 添加到检测结果队列
func AddCheckResult(checkResult CheckResult) {
	muCheckQueue.Lock()
	checkResultQueue.Enqueue(checkResult)
	defer muCheckQueue.Unlock()
}

// 获取检测结果
func GetCheckResult() (CheckResult, error) {
	muCheckQueue.Lock()
	defer muCheckQueue.Unlock()
	if checkResultQueue.Len() == 0 {
		return CheckResult{}, errors.New("队列为空")
	}
	return checkResultQueue.Dequeue().(CheckResult), nil
}

// 连接MQ，返回连接对象已经msgs通道
func ConnectMQ(mqURL, queueName string) (*amqp.Connection, <-chan amqp.Delivery, error) {
	conn, err := amqp.Dial(mqURL)
	if err != nil {
		return nil, nil, err
	}

	ch, err := conn.Channel()
	if err != nil {
		return nil, nil, err
	}

	// 设置每次只接收 10 条消息
	err = ch.Qos(10, 0, false)
	if err != nil {
		return nil, nil, err
	}

	msgs, err := ch.Consume(
		queueName,
		"",
		false, // note: 关闭自动确认
		false,
		false,
		false,
		nil,
	)
	if err != nil {
		ch.Close()
		conn.Close()
		return nil, nil, err
	}
	return conn, msgs, nil
}

// 线程1：添加检测+开始检测，从rabbit_mq中获取字段，然后将id存储到检测中check_id数组中
func Thread1AddCheckAndStartDetect(mqURL, queueName, inputLanguage string, checkServiceConfig *CheckServiceBucket, client check.CheckClient, statisticsUtils *Statistics) {
	exception.LogInfo("线程1启动")
	conn, msgs, err := ConnectMQ(mqURL, queueName)
	if err != nil {
		exception.LogError(exception.NewSystemError("连接MQ失败" + err.Error()))
		return
	}
	// 定义析构函数，当 conn为空时，不执行.Close()方法
	if conn == nil || msgs == nil {
		exception.LogError(exception.NewSystemError("连接对象或者消息通道为空"))
		return
	}
	defer conn.Close()
	for msg := range msgs {
		var getMsg TranslationMessage
		if err := json.Unmarshal(msg.Body, &getMsg); err != nil {
			exception.LogError(exception.NewSystemError("解析消息失败" + err.Error()))
			msg.Nack(false, true)
			return
		}
		start := time.Now()
		// 添加检测获取checkId
		checkId, err := checkServiceConfig.AddCheck(inputLanguage, client, getMsg.PaperID, getMsg.Content)
		if err != nil {
			exception.LogError(exception.NewProgramError("调用 AddCheck 失败" + err.Error()))
			msg.Nack(false, true)
			continue
		}
		// 开始检测
		waitTime, err := checkServiceConfig.Check(client, checkId)
		if err != nil {
			exception.LogError(exception.NewProgramError("调用 Check 失败" + err.Error()))
			msg.Nack(false, true)
			continue
		}
		// 统计时间
		statisticsUtils.AddStats("送检并开始检测", &ModuleStats{
			TotalTime:      time.Since(start),
			TotalCount:     1,
			TotalCharCount: int64(len(getMsg.Content)),
		})

		exception.LogInfo(fmt.Sprintf("检测ID: %s, 等待时间: %d,检测语言为 %s,论文ID: %s", checkId, waitTime, inputLanguage, getMsg.PaperID))

		AddCheckId(CheckResult{
			checkId:       checkId,
			paperId:       getMsg.PaperID,
			targetContent: getMsg.Content,
			checkLanguage: inputLanguage,
		})
		exception.LogInfo(fmt.Sprintf("添加检测ID: %s,论文ID: %s", checkId, getMsg.PaperID))

		// 确认消息
		msg.Ack(false)
	}
}

// 线程2：获取检测状态，取check_id数组中的id，获取检测状态，将已经成功的id从检测中数组移动到结果中数组（使用队列）
func Thread2GetCheckStatus(checkServiceConfig *CheckServiceBucket, client check.CheckClient, statisticsUtils *Statistics) {
	exception.LogInfo("线程2启动")
	// 定义一个小组长度为5
	groupLength := 5

	for {
		// 如果检测中数组为空，等待1s
		if len(checkIdArray) == 0 {
			statisticsUtils.AddStats("checkId检测中队列为空等待", &ModuleStats{
				TotalTime:      1 * time.Second,
				TotalCount:     1,
				TotalCharCount: 0,
			})

			time.Sleep(1 * time.Second)
			continue
		}

		// 取出小组
		var group []CheckResult
		var err error
		if len(checkIdArray) < groupLength {
			group, err = GetCheckIdArray(0, len(checkIdArray))
			if err != nil {
				exception.LogError(exception.NewSystemError("取数组失败" + err.Error()))
				return
			}
		} else {
			group, err = GetCheckIdArray(0, groupLength)
			if err != nil {
				exception.LogError(exception.NewSystemError("取数组失败" + err.Error()))
				return
			}
		}
		// 取出小组后，将小组中的check_id取出来
		var checkIdGroup []string
		for _, checkResult := range group {
			checkIdGroup = append(checkIdGroup, checkResult.checkId)
		}
		start := time.Now()
		// 获取检测状态
		checkStatus, err := checkServiceConfig.GetCheckStatus(client, checkIdGroup)
		if err != nil {
			exception.LogError(exception.NewProgramError("调用 GetCheckStatus 失败" + err.Error()))
			continue
		}
		// 统计时间
		statisticsUtils.AddStats("获取检测状态", &ModuleStats{
			TotalTime:      time.Since(start),
			TotalCount:     1,
			TotalCharCount: 0,
		})

		// 遍历检测状态，如果检测完成，将check_id移动到结果队列，并记录失败的下标，保证连续删除
		var failIndex []int
		for _, status := range checkStatus {
			var findIndex = -1
			// note: 输入顺序和输出顺序不一致，需要根据checkId定位
			for j, checkResult := range group {
				if checkResult.checkId == status.CheckId {
					// 将小组中的检测结果移动到结果队列
					// 添加检测的查重率
					findIndex = j
					break
				}
			}
			if findIndex == -1 {
				exception.LogError(exception.NewSystemError("未找到对应的checkId"))
				return
			}
			if status.CheckState == check.CheckState_SUCCESS {
				group[findIndex].copyPercent = status.CopyPercent
				AddCheckResult(group[findIndex])
			} else {
				failIndex = append(failIndex, findIndex)
			}
		}
		// 直接将该小组删除，然后失败的重新加入检测中数组末尾进行下一次的检测
		for _, index := range failIndex {
			start := time.Now()
			AddCheckId(group[index])
			// 统计时间
			statisticsUtils.AddStats("失败状态重新加入检测数组", &ModuleStats{
				TotalTime:      time.Since(start),
				TotalCount:     1,
				TotalCharCount: 0,
			})
		}
		// note: 一定使用len(group)而不是groupLength，因为groupLength可能会大于checkIdArray的长度
		err = RemoveCheckIdArray(0, len(group))
		if err != nil {
			exception.LogError(exception.NewSystemError("删除数组失败" + err.Error()))
			return
		}

		// 执行完毕，等待
		statisticsUtils.AddStats("成组获取检测状态后等待", &ModuleStats{
			TotalTime:      500 * time.Millisecond,
			TotalCount:     1,
			TotalCharCount: 0,
		})

		time.Sleep(500 * time.Millisecond)
	}
}

// 线程3：获取检测结果，取check_id数组中的id，获取检测结果，将获取完成的id从队列中移除
func Thread3GetCheckResult(checkServiceConfig *CheckServiceBucket, client check.CheckClient, statisticsUtils *Statistics) {
	exception.LogInfo("线程3启动")
	for {
		// 如果检测结果队列为空，等待1s
		if checkResultQueue.Len() == 0 {
			statisticsUtils.AddStats("checkResult检测结果队列为空等待", &ModuleStats{
				TotalTime:      1 * time.Second,
				TotalCount:     1,
				TotalCharCount: 0,
			})

			time.Sleep(1 * time.Second)
			continue
		}

		// 获取检测结果
		checkResult, err := GetCheckResult()
		if err != nil {
			exception.LogError(exception.NewSystemError("获取检测结果失败" + err.Error()))
			return
		}

		start := time.Now()
		// 获取检测结果
		checkResultStr, err := checkServiceConfig.GetCheckResult(client, checkResult.checkId)
		if err != nil {
			exception.LogError(exception.NewProgramError("调用 GetCheckResult 失败" + err.Error()))
			continue
		}
		// 统计时间
		statisticsUtils.AddStats("获取检测结果", &ModuleStats{
			TotalTime:      time.Since(start),
			TotalCount:     1,
			TotalCharCount: 0,
		})

		start = time.Now()
		// 将检测结果保存到文件中
		saveCheckResultToFile(checkResult, checkResultStr)
		// 统计时间
		statisticsUtils.AddStats("保存检测结果", &ModuleStats{
			TotalTime:      time.Since(start),
			TotalCount:     1,
			TotalCharCount: int64(len(checkResultStr)),
		})
	}
}

// 从配置文件中读取 en_check_path 和 ch_check_path
func loadCheckPaths() FileSavePath {
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath("config")

	if err := viper.ReadInConfig(); err != nil {
		exception.LogError(exception.NewSystemError("读取配置文件失败" + err.Error()))
		return FileSavePath{}
	}

	enCheckPath := viper.GetString("file_save.en_check_path")
	chCheckPath := viper.GetString("file_save.cn_check_path")

	return FileSavePath{
		EnCheckPath: enCheckPath,
		ChCheckPath: chCheckPath,
	}
}

// 将检测结果保存到文件中
func saveCheckResultToFile(checkResult CheckResult, checkResultJsonString string) {
	paths := loadCheckPaths()

	var filePath string
	// 将paperId + 查重率的前5位 作为文件名
	if checkResult.checkLanguage == English {
		filePath = fmt.Sprintf("%s/%s.json", paths.EnCheckPath, checkResult.paperId+fmt.Sprintf("_%.5f", checkResult.copyPercent))
	} else if checkResult.checkLanguage == Chinese {
		filePath = fmt.Sprintf("%s/%s.json", paths.ChCheckPath, checkResult.paperId+fmt.Sprintf("_%.5f", checkResult.copyPercent))
	}

	file, err := os.Create(filePath)
	if err != nil {
		exception.LogError(exception.NewSystemError("创建文件失败" + err.Error()))
		return
	}
	defer file.Close()

	var checkResultJson map[string]interface{}
	if err := json.Unmarshal([]byte(checkResultJsonString), &checkResultJson); err != nil {
		exception.LogError(exception.NewSystemError("解析JSON字符串失败" + err.Error()))
		return
	}
	encoder := json.NewEncoder(file)
	if err := encoder.Encode(checkResultJson); err != nil {
		exception.LogError(exception.NewSystemError("写入文件失败" + err.Error()))
		return
	}
}

// 启动服务器
func StartCheckService(mqURL, chCheckQueue, enCheckQueue string, checkServiceConfig *CheckServiceBucket, client check.CheckClient, statisticsUtils *Statistics) {
	// 开启线程1，2，3
	// 中文查重检测
	go Thread1AddCheckAndStartDetect(mqURL, chCheckQueue, Chinese, checkServiceConfig, client, statisticsUtils)
	// TODO: 英文查重检测，测试时不查询英文了，因为基本没有
	// go Thread1AddCheckAndStartDetect(mqURL, enCheckQueue, English, checkServiceConfig, client, statisticsUtils)
	go Thread2GetCheckStatus(checkServiceConfig, client, statisticsUtils)
	go Thread3GetCheckResult(checkServiceConfig, client, statisticsUtils)
}
